﻿using System.Collections.Concurrent;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;

namespace Dyao.Book.Repository.Shared
{
    /// <summary>
    /// 匿名类构造器
    /// </summary>
    public static class AnonymousTypeBuilder
    {
        private static AssemblyName _assemblyName = new AssemblyName() { Name = "IMC.AnonymousTypes" };
        private static ModuleBuilder _moduleBuilder = null;
        private static ConcurrentDictionary<string, TypeInfo> _anonymousTypes = new ConcurrentDictionary<string, TypeInfo>();

        static AnonymousTypeBuilder()
        {
            var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(_assemblyName, AssemblyBuilderAccess.Run);

            _moduleBuilder = assemblyBuilder.DefineDynamicModule(_assemblyName.Name + ".Contract");
        }

        public static TypeInfo GetOrCreateDynamicType(IList<MemberInfo> members)
        {
            if (null == members) throw new ArgumentNullException("members");
            if (members.Count == 0) throw new ArgumentOutOfRangeException("members", "members must have at least 1 field definition");
            if (members.Distinct().Count() != members.Count) throw new ArgumentException("members are not allowed to duplicate.");
            if (members.Any(a => a.MemberType != MemberTypes.Property && a.MemberType != MemberTypes.Field)) throw new ArgumentException("members only support Propery and Field.");

            var className = CreateTypeCacheKey(members);

            var typeInfo = _anonymousTypes.GetOrAdd(className, (name) =>
            {
                var className2 = "$AnonymousTypeN" + Guid.NewGuid().ToString("N");
                var typeBuilder = _moduleBuilder.DefineType(className2, TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.Serializable);

                var ctorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, members.Select(a => a is PropertyInfo p ? p.PropertyType : a is FieldInfo f ? f.FieldType : null).ToArray());
                var ctorIl = ctorBuilder.GetILGenerator();

                ctorIl.Emit(OpCodes.Ldarg_0);
                ctorIl.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes));

                var argIndex = 1;

                var getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
                foreach (var item in members)
                {
                    if (item is PropertyInfo p)
                    {
                        var fieldBuilder = typeBuilder.DefineField($"<{p.Name}>k__BackingField", p.PropertyType, FieldAttributes.Private); //私有字段创建

                        var pBuilder = typeBuilder.DefineProperty(item.Name, PropertyAttributes.None, p.PropertyType, null); //属性创建

                        var getBuilder = typeBuilder.DefineMethod($"get_{item.Name}", getSetAttr, p.PropertyType, Type.EmptyTypes); // get 方法

                        // get 实现
                        var getIl = getBuilder.GetILGenerator();
                        getIl.Emit(OpCodes.Ldarg_0);
                        getIl.Emit(OpCodes.Ldfld, fieldBuilder);
                        getIl.Emit(OpCodes.Ret);

                        var setBuilder = typeBuilder.DefineMethod($"set_{item.Name}", getSetAttr, null, new Type[] { p.PropertyType }); // set 方法
                        // set 实现
                        var setIl = setBuilder.GetILGenerator();
                        setIl.Emit(OpCodes.Ldarg_0);
                        setIl.Emit(OpCodes.Ldarg_1);
                        setIl.Emit(OpCodes.Stfld, fieldBuilder);
                        setIl.Emit(OpCodes.Ret);

                        pBuilder.SetGetMethod(getBuilder);
                        pBuilder.SetSetMethod(setBuilder);

                        //构造方法参数赋值
                        ctorIl.Emit(OpCodes.Ldarg_0);
                        LoadArg(ctorIl, argIndex);
                        ctorIl.Emit(OpCodes.Stfld, fieldBuilder);
                    }
                    else if (item is FieldInfo f)
                    {
                        var fieldBuilder = typeBuilder.DefineField(item.Name, f.FieldType, FieldAttributes.Public);

                        ctorIl.Emit(OpCodes.Ldarg_0);
                        LoadArg(ctorIl, argIndex);
                        ctorIl.Emit(OpCodes.Stfld, fieldBuilder);
                    }

                    argIndex++;
                }

                ctorIl.Emit(OpCodes.Ret);

                return typeBuilder.CreateTypeInfo();
            });

            return typeInfo;
        }

        private static string CreateTypeCacheKey(IList<MemberInfo> members)
        {
            var modelType = members[0].DeclaringType;

            var sb = new StringBuilder();

            sb.Append(modelType.FullName);
            sb.Append($":<>f__AnonymousTypeN`{members.Count}(");

            foreach (var item in members)
            {
                sb.Append(item.Name).Append('|');
            }

            sb[sb.Length - 1] = ')';

            return sb.ToString();
        }

        private static void LoadArg(ILGenerator ilGenerator, int argIndex)
        {
            switch (argIndex)
            {
                case 0:
                    ilGenerator.Emit(OpCodes.Ldarg_0);
                    return;
                case 1:
                    ilGenerator.Emit(OpCodes.Ldarg_1);
                    return;
                case 2:
                    ilGenerator.Emit(OpCodes.Ldarg_2);
                    return;
                case 3:
                    ilGenerator.Emit(OpCodes.Ldarg_3);
                    return;
                default:
                    ilGenerator.Emit(argIndex < 256 ? OpCodes.Ldarg_S : OpCodes.Ldarg, argIndex); break;
            }
        }
    }
}
